// Copyright 2008-present Contributors to the OpenImageIO project.
// SPDX-License-Identifier: BSD-3-Clause
// https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <numeric>

#include <OpenEXR/half.h>

#include <OpenImageIO/dassert.h>
#include <OpenImageIO/deepdata.h>
#include <OpenImageIO/imageio.h>
#include <OpenImageIO/strutil.h>
#include <OpenImageIO/thread.h>

OIIO_NAMESPACE_BEGIN


// Each pixel has a capacity (number of samples allocated) and a number of
// samples currently used. Erasing samples only reduces the samples in the
// pixels without changing the capacity, so there is no reallocation or data
// movement except for that pixel. Samples can be added without any
// reallocation or copying data (other than that one pixel) unles the
// capacity of the pixel is exceeded. Furthermore, only changes in capacity
// need to lock the mutex. As long as capacity is not changing, threads may
// change number of samples (inserting or deleting) as well as altering
// data, simultaneously, as long as they are working on separate pixels.



class DeepData::Impl {  // holds all the nontrivial stuff
    friend class DeepData;

public:
    std::vector<TypeDesc> m_channeltypes;  // for each channel [c]
    std::vector<size_t> m_channelsizes;    // for each channel [c]
    std::vector<size_t> m_channeloffsets;  // for each channel [c]
    std::vector<unsigned int> m_nsamples;  // for each pixel [p]
    std::vector<unsigned int> m_capacity;  // for each pixel [p]
    std::vector<unsigned int>
        m_cumcapacity;         // cumulative capacity before pixel [p]
    std::vector<char> m_data;  // for each sample [p][s][c]
    std::vector<std::string> m_channelnames;  // For each channel[c]
    std::vector<int> m_myalphachannel;        // For each channel[c], its alpha
        // myalphachannel[c] gives the alpha channel corresponding to channel
        // c, or c if it is itself an alpha, or -1 if it doesn't appear to
        // be a color channel at all.
    size_t m_samplesize;
    int m_z_channel, m_zback_channel;
    int m_alpha_channel;
    int m_AR_channel;
    int m_AG_channel;
    int m_AB_channel;
    bool m_allocated;
    spin_mutex m_mutex;

    Impl()
        : m_allocated(false)
    {
        clear();
    }

    void clear()
    {
        m_channeltypes.clear();
        m_channelsizes.clear();
        m_channeloffsets.clear();
        m_nsamples.clear();
        m_capacity.clear();
        m_cumcapacity.clear();
        m_data.clear();
        m_channelnames.clear();
        m_myalphachannel.clear();
        m_samplesize    = 0;
        m_z_channel     = -1;
        m_zback_channel = -1;
        m_alpha_channel = -1;
        m_AR_channel    = -1;
        m_AG_channel    = -1;
        m_AB_channel    = -1;
        m_allocated     = false;
    }

    // If not already done, allocate data and cumcapacity
    void alloc(size_t npixels)
    {
        if (!m_allocated) {
            spin_lock lock(m_mutex);
            if (!m_allocated) {
                // m_cumcapacity.resize (npixels);
                size_t totalcapacity = 0;
                for (size_t i = 0; i < npixels; ++i) {
                    m_cumcapacity[i] = totalcapacity;
                    totalcapacity += m_capacity[i];
                }
                m_data.resize(totalcapacity * m_samplesize);
                m_allocated = true;
            }
        }
    }

    size_t data_offset(int64_t pixel, int channel, int sample)
    {
        OIIO_DASSERT(int(m_cumcapacity.size()) > pixel);
        OIIO_DASSERT(m_capacity[pixel] >= m_nsamples[pixel]);
        return (m_cumcapacity[pixel] + sample) * m_samplesize
               + m_channeloffsets[channel];
    }

    void* data_ptr(int64_t pixel, int channel, int sample)
    {
        size_t offset = data_offset(pixel, channel, sample);
        OIIO_DASSERT(offset < m_data.size());
        return &m_data[offset];
    }

    size_t total_capacity() const
    {
        return m_cumcapacity.back() + m_capacity.back();
    }

    inline void sanity() const
    {
        // int nchannels = int (m_channeltypes.size());
        OIIO_ASSERT(m_channeltypes.size() == m_channelsizes.size());
        OIIO_ASSERT(m_channeltypes.size() == m_channeloffsets.size());
        int64_t npixels = int64_t(m_capacity.size());
        OIIO_ASSERT(m_nsamples.size() == m_capacity.size());
        OIIO_ASSERT(m_cumcapacity.size() == m_capacity.size());
        if (m_allocated) {
            size_t totalcapacity = 0;
            for (int64_t p = 0; p < npixels; ++p) {
                OIIO_ASSERT(m_cumcapacity[p] == totalcapacity);
                totalcapacity += m_capacity[p];
                OIIO_ASSERT(m_capacity[p] >= m_nsamples[p]);
            }
            OIIO_ASSERT(totalcapacity * m_samplesize == m_data.size());
        }
    }
};



DeepData::DeepData()
    : m_impl(NULL)
    , m_npixels(0)
    , m_nchannels(0)
{
}



DeepData::DeepData(const ImageSpec& spec)
    : m_impl(NULL)
{
    init(spec);
}



DeepData::~DeepData() { delete m_impl; }



DeepData::DeepData(const DeepData& d)
    : m_impl(NULL)
{
    m_npixels   = d.m_npixels;
    m_nchannels = d.m_nchannels;
    if (d.m_impl) {
        m_impl  = new Impl;
        *m_impl = *(d.m_impl);
    }
}



const DeepData&
DeepData::operator=(const DeepData& d)
{
    if (this != &d) {
        m_npixels   = d.m_npixels;
        m_nchannels = d.m_nchannels;
        if (!m_impl)
            m_impl = new Impl;
        if (d.m_impl)
            *m_impl = *(d.m_impl);
        else
            m_impl->clear();
    }
    return *this;
}



int64_t
DeepData::pixels() const
{
    return m_npixels;
}



int
DeepData::channels() const
{
    return m_nchannels;
}



int
DeepData::Z_channel() const
{
    return m_impl->m_z_channel;
}



int
DeepData::Zback_channel() const
{
    return m_impl->m_zback_channel >= 0 ? m_impl->m_zback_channel
                                        : m_impl->m_z_channel;
}



int
DeepData::A_channel() const
{
    return m_impl->m_alpha_channel;
}



int
DeepData::AR_channel() const
{
    return m_impl->m_AR_channel >= 0 ? m_impl->m_AR_channel
                                     : m_impl->m_alpha_channel;
}



int
DeepData::AG_channel() const
{
    return m_impl->m_AG_channel >= 0 ? m_impl->m_AG_channel
                                     : m_impl->m_alpha_channel;
}



int
DeepData::AB_channel() const
{
    return m_impl->m_AB_channel >= 0 ? m_impl->m_AB_channel
                                     : m_impl->m_alpha_channel;
}



string_view
DeepData::channelname(int c) const
{
    OIIO_DASSERT(m_impl);
    return (c >= 0 && c < m_nchannels) ? string_view(m_impl->m_channelnames[c])
                                       : string_view();
}



TypeDesc
DeepData::channeltype(int c) const
{
    OIIO_DASSERT(m_impl);
    return (c >= 0 && c < m_nchannels) ? m_impl->m_channeltypes[c] : TypeDesc();
}



size_t
DeepData::channelsize(int c) const
{
    OIIO_DASSERT(m_impl);
    return (c >= 0 && c < m_nchannels) ? m_impl->m_channelsizes[c] : 0;
}



size_t
DeepData::samplesize() const
{
    return m_impl->m_samplesize;
}



// Is name the same as suffix, or does it end in ".suffix"?
inline bool
is_or_endswithdot(string_view name, string_view suffix)
{
    return (Strutil::iequals(name, suffix)
            || (name.size() > suffix.size() && Strutil::iends_with(name, suffix)
                && name[name.size() - suffix.size() - 1] == '.'));
}



void
DeepData::init(int64_t npix, int nchan, cspan<TypeDesc> channeltypes,
               cspan<std::string> channelnames)
{
    clear();
    m_npixels   = npix;
    m_nchannels = nchan;
    OIIO_ASSERT(channeltypes.size() >= 1);
    if (!m_impl)
        m_impl = new Impl;
    if (int(channeltypes.size()) >= nchan) {
        m_impl->m_channeltypes.assign(channeltypes.data(),
                                      channeltypes.data() + nchan);
    } else {
        m_impl->m_channeltypes.clear();
        m_impl->m_channeltypes.resize(m_nchannels, channeltypes[0]);
    }
    m_impl->m_channelsizes.resize(m_nchannels);
    m_impl->m_channeloffsets.resize(m_nchannels);
    m_impl->m_channelnames.resize(m_nchannels);
    m_impl->m_myalphachannel.resize(m_nchannels, -1);
    m_impl->m_samplesize = 0;
    m_impl->m_nsamples.resize(m_npixels, 0);
    m_impl->m_capacity.resize(m_npixels, 0);
    m_impl->m_cumcapacity.resize(m_npixels, 0);

    // Channel name hunt
    // First, find Z, Zback, A
    for (int c = 0; c < m_nchannels; ++c) {
        size_t size                 = m_impl->m_channeltypes[c].size();
        m_impl->m_channelsizes[c]   = size;
        m_impl->m_channeloffsets[c] = m_impl->m_samplesize;
        m_impl->m_samplesize += size;
        m_impl->m_channelnames[c] = channelnames[c];
        if (m_impl->m_z_channel < 0 && is_or_endswithdot(channelnames[c], "Z"))
            m_impl->m_z_channel = c;
        else if (m_impl->m_zback_channel < 0
                 && is_or_endswithdot(channelnames[c], "Zback"))
            m_impl->m_zback_channel = c;
        else if (m_impl->m_alpha_channel < 0
                 && is_or_endswithdot(channelnames[c], "A"))
            m_impl->m_alpha_channel = c;
        else if (m_impl->m_alpha_channel < 0
                 && is_or_endswithdot(channelnames[c], "Alpha"))
            m_impl->m_alpha_channel = c;
        else if (m_impl->m_AR_channel < 0
                 && is_or_endswithdot(channelnames[c], "AR"))
            m_impl->m_AR_channel = c;
        else if (m_impl->m_AG_channel < 0
                 && is_or_endswithdot(channelnames[c], "AG"))
            m_impl->m_AG_channel = c;
        else if (m_impl->m_AB_channel < 0
                 && is_or_endswithdot(channelnames[c], "AB"))
            m_impl->m_AB_channel = c;
    }
    // Now try to find which alpha corresponds to each channel
    for (int c = 0; c < m_nchannels; ++c) {
        // Skip non-color channels
        if (c == m_impl->m_z_channel || c == m_impl->m_zback_channel
            || m_impl->m_channeltypes[c] == TypeDesc::UINT32)
            continue;
        string_view name(channelnames[c]);
        // Alpha channels are their own alpha
        if (is_or_endswithdot(name, "A") || is_or_endswithdot(name, "AR")
            || is_or_endswithdot(name, "AG") || is_or_endswithdot(name, "AB")
            || is_or_endswithdot(name, "Alpha")) {
            m_impl->m_myalphachannel[c] = c;
            continue;
        }
        // For anything else, try to find its channel
        string_view prefix = name, suffix = name;
        size_t dot = name.find_last_of('.');
        if (dot == string_view::npos) {  // No dot
            prefix.clear();
        } else {  // dot
            prefix = prefix.substr(0, dot + 1);
            suffix = suffix.substr(dot + 1);
        }
        std::string targetalpha = std::string(prefix) + "A"
                                  + std::string(suffix);
        for (int i = 0; i < m_nchannels; ++i) {
            if (Strutil::iequals(m_impl->m_channelnames[i], targetalpha)) {
                m_impl->m_myalphachannel[c] = i;
                break;
            }
        }
        if (m_impl->m_myalphachannel[c] < 0)
            m_impl->m_myalphachannel[c] = m_impl->m_alpha_channel;
    }
}



void
DeepData::init(const ImageSpec& spec)
{
    if (int(spec.channelformats.size()) == spec.nchannels)
        init((int)spec.image_pixels(), spec.nchannels, spec.channelformats,
             spec.channelnames);
    else
        init((int)spec.image_pixels(), spec.nchannels, spec.format,
             spec.channelnames);
}



void
DeepData::clear()
{
    m_npixels   = 0;
    m_nchannels = 0;
    if (m_impl)
        m_impl->clear();
}



void
DeepData::free()
{
    clear();
    delete m_impl;
    m_impl = NULL;
}



bool
DeepData::initialized() const
{
    return m_impl != nullptr;
}



bool
DeepData::allocated() const
{
    return m_impl && m_impl->m_allocated;
}



int
DeepData::capacity(int64_t pixel) const
{
    if (pixel < 0 || pixel >= m_npixels)
        return 0;
    OIIO_DASSERT(m_impl && m_impl->m_capacity.size() > size_t(pixel));
    return m_impl->m_capacity[pixel];
}



void
DeepData::set_capacity(int64_t pixel, int samps)
{
    if (pixel < 0 || pixel >= m_npixels)
        return;
    OIIO_DASSERT(m_impl);
    spin_lock lock(m_impl->m_mutex);
    if (m_impl->m_allocated) {
        // Data already allocated. Expand capacity if necessary, don't
        // contract. (FIXME?)
        int n = (int)capacity(pixel);
        if (samps > n) {
            int toadd = samps - n;
            if (m_impl->m_data.empty()) {
                size_t newtotal = (m_impl->total_capacity() + toadd);
                m_impl->m_data.resize(newtotal * samplesize());
            } else {
                size_t offset = m_impl->data_offset(pixel, 0, n);
                m_impl->m_data.insert(m_impl->m_data.begin() + offset,
                                      toadd * samplesize(), 0);
            }
            // Adjust the cumulative prefix sum of samples for subsequent pixels
            for (int64_t p = pixel + 1; p < m_npixels; ++p)
                m_impl->m_cumcapacity[p] += toadd;
            m_impl->m_capacity[pixel] = samps;
        }
    } else {
        m_impl->m_capacity[pixel] = samps;
    }
}



int
DeepData::samples(int64_t pixel) const
{
    if (pixel < 0 || pixel >= m_npixels)
        return 0;
    OIIO_DASSERT(m_impl);
    return m_impl->m_nsamples[pixel];
}



void
DeepData::set_samples(int64_t pixel, int samps)
{
    if (pixel < 0 || pixel >= m_npixels)
        return;
    OIIO_DASSERT(m_impl);
    if (m_impl->m_allocated) {
        // Data already allocated. Turn it into an insert or delete
        int n = (int)m_impl->m_nsamples[pixel];
        if (samps > n)
            insert_samples(pixel, n, samps - n);
        else if (samps < n)
            erase_samples(pixel, samps, n - samps);
    } else {
        m_impl->m_nsamples[pixel] = samps;
        m_impl->m_capacity[pixel] = std::max(unsigned(samps),
                                             m_impl->m_capacity[pixel]);
    }
}



void
DeepData::set_all_samples(cspan<unsigned int> samples)
{
    if (samples.size() != m_npixels)
        return;
    OIIO_DASSERT(m_impl);
    if (m_impl->m_allocated) {
        // Data already allocated: set pixels individually
        for (int64_t p = 0; p < m_npixels; ++p)
            set_samples(p, int(samples[p]));
    } else {
        // Data not yet allocated: copy in one shot
        m_impl->m_nsamples.assign(&samples[0], &samples[m_npixels]);
        m_impl->m_capacity.assign(&samples[0], &samples[m_npixels]);
    }
}



void
DeepData::insert_samples(int64_t pixel, int samplepos, int n)
{
    int oldsamps = samples(pixel);
    if (oldsamps + n > int(m_impl->m_capacity[pixel]))
        set_capacity(pixel, oldsamps + n);
    // set_capacity is thread-safe, it locks internally. Once the capacity
    // is adjusted, we can alter nsamples or copy the data around within
    // the pixel without a lock, we presume that if multiple threads are
    // in play, they are working on separate pixels.
    if (m_impl->m_allocated) {
        // Move the data
        if (samplepos < oldsamps) {
            size_t offset = m_impl->data_offset(pixel, 0, samplepos);
            size_t end    = m_impl->data_offset(pixel, 0, oldsamps);
            std::copy_backward(m_impl->m_data.begin() + offset,
                               m_impl->m_data.begin() + end,
                               m_impl->m_data.begin() + end + n * samplesize());
        }
    }
    // Add to this pixel's sample count
    m_impl->m_nsamples[pixel] += n;
}



void
DeepData::erase_samples(int64_t pixel, int samplepos, int n)
{
    // DON'T reduce the capacity! Just leave holes for speed.
    // Because erase_samples only moves data within a pixel and doesn't
    // change the capacity, no lock is needed.
    n = std::min(n, int(m_impl->m_nsamples[pixel]));
    if (m_impl->m_allocated) {
        // Move the data
        int oldsamps  = samples(pixel);
        size_t offset = m_impl->data_offset(pixel, 0, samplepos);
        size_t end    = m_impl->data_offset(pixel, 0, oldsamps);
        std::copy(m_impl->m_data.begin() + offset + n * samplesize(),
                  m_impl->m_data.begin() + end,
                  m_impl->m_data.begin() + offset);
    }
    m_impl->m_nsamples[pixel] -= n;
}



void*
DeepData::data_ptr(int64_t pixel, int channel, int sample)
{
    m_impl->alloc(m_npixels);
    if (pixel < 0 || pixel >= m_npixels || channel < 0 || channel >= m_nchannels
        || !m_impl || sample < 0 || sample >= int(m_impl->m_nsamples[pixel]))
        return NULL;
    return m_impl->data_ptr(pixel, channel, sample);
}



const void*
DeepData::data_ptr(int64_t pixel, int channel, int sample) const
{
    if (pixel < 0 || pixel >= m_npixels || channel < 0 || channel >= m_nchannels
        || !m_impl || !m_impl->m_data.size() || sample < 0
        || sample >= int(m_impl->m_nsamples[pixel]))
        return NULL;
    return m_impl->data_ptr(pixel, channel, sample);
}



float
DeepData::deep_value(int64_t pixel, int channel, int sample) const
{
    const void* ptr = data_ptr(pixel, channel, sample);
    if (!ptr)
        return 0.0f;
    switch (channeltype(channel).basetype) {
    case TypeDesc::FLOAT: return ((const float*)ptr)[0];
    case TypeDesc::HALF: return ((const half*)ptr)[0];
    case TypeDesc::UINT:
        return ConstDataArrayProxy<unsigned int, float>(
            (const unsigned int*)ptr)[0];
    case TypeDesc::UINT8:
        return ConstDataArrayProxy<unsigned char, float>(
            (const unsigned char*)ptr)[0];
    case TypeDesc::INT8:
        return ConstDataArrayProxy<char, float>((const char*)ptr)[0];
    case TypeDesc::UINT16:
        return ConstDataArrayProxy<unsigned short, float>(
            (const unsigned short*)ptr)[0];
    case TypeDesc::INT16:
        return ConstDataArrayProxy<short, float>((const short*)ptr)[0];
    case TypeDesc::INT:
        return ConstDataArrayProxy<int, float>((const int*)ptr)[0];
    case TypeDesc::UINT64:
        return ConstDataArrayProxy<unsigned long long, float>(
            (const unsigned long long*)ptr)[0];
    case TypeDesc::INT64:
        return ConstDataArrayProxy<long long, float>((const long long*)ptr)[0];
    default:
        OIIO_ASSERT_MSG(0, "Unknown/unsupported data type %d",
                        channeltype(channel).basetype);
        return 0.0f;
    }
}



uint32_t
DeepData::deep_value_uint(int64_t pixel, int channel, int sample) const
{
    const void* ptr = data_ptr(pixel, channel, sample);
    if (!ptr)
        return 0;
    switch (channeltype(channel).basetype) {
    case TypeDesc::UINT: return ((const unsigned int*)ptr)[0];
    case TypeDesc::FLOAT:
        return ConstDataArrayProxy<float, uint32_t>((const float*)ptr)[0];
    case TypeDesc::HALF:
        return ConstDataArrayProxy<half, uint32_t>((const half*)ptr)[0];
    case TypeDesc::UINT8:
        return ConstDataArrayProxy<unsigned char, uint32_t>(
            (const unsigned char*)ptr)[0];
    case TypeDesc::INT8:
        return ConstDataArrayProxy<char, uint32_t>((const char*)ptr)[0];
    case TypeDesc::UINT16:
        return ConstDataArrayProxy<unsigned short, uint32_t>(
            (const unsigned short*)ptr)[0];
    case TypeDesc::INT16:
        return ConstDataArrayProxy<short, uint32_t>((const short*)ptr)[0];
    case TypeDesc::INT:
        return ConstDataArrayProxy<int, uint32_t>((const int*)ptr)[0];
    case TypeDesc::UINT64:
        return ConstDataArrayProxy<unsigned long long, uint32_t>(
            (const unsigned long long*)ptr)[0];
    case TypeDesc::INT64:
        return ConstDataArrayProxy<long long, uint32_t>(
            (const long long*)ptr)[0];
    default:
        OIIO_ASSERT_MSG(0, "Unknown/unsupported data type %d",
                        channeltype(channel).basetype);
        return 0;
    }
}



void
DeepData::set_deep_value(int64_t pixel, int channel, int sample, float value)
{
    void* ptr = data_ptr(pixel, channel, sample);
    if (!ptr)
        return;
    switch (channeltype(channel).basetype) {
    case TypeDesc::FLOAT:
        DataArrayProxy<float, float>((float*)ptr)[0] = value;
        break;
    case TypeDesc::HALF:
        DataArrayProxy<half, float>((half*)ptr)[0] = value;
        break;
    case TypeDesc::UINT:
        DataArrayProxy<uint32_t, float>((uint32_t*)ptr)[0] = value;
        break;
    case TypeDesc::UINT8:
        DataArrayProxy<unsigned char, float>((unsigned char*)ptr)[0] = value;
        break;
    case TypeDesc::INT8:
        DataArrayProxy<char, float>((char*)ptr)[0] = value;
        break;
    case TypeDesc::UINT16:
        DataArrayProxy<unsigned short, float>((unsigned short*)ptr)[0] = value;
        break;
    case TypeDesc::INT16:
        DataArrayProxy<short, float>((short*)ptr)[0] = value;
        break;
    case TypeDesc::INT: DataArrayProxy<int, float>((int*)ptr)[0] = value; break;
    case TypeDesc::UINT64:
        DataArrayProxy<uint64_t, float>((uint64_t*)ptr)[0] = value;
        break;
    case TypeDesc::INT64:
        DataArrayProxy<int64_t, float>((int64_t*)ptr)[0] = value;
        break;
    default:
        OIIO_ASSERT_MSG(0, "Unknown/unsupported data type %d",
                        channeltype(channel).basetype);
    }
}



void
DeepData::set_deep_value(int64_t pixel, int channel, int sample, uint32_t value)
{
    void* ptr = data_ptr(pixel, channel, sample);
    if (!ptr)
        return;
    switch (channeltype(channel).basetype) {
    case TypeDesc::FLOAT:
        DataArrayProxy<float, uint32_t>((float*)ptr)[0] = value;
        break;
    case TypeDesc::HALF:
        DataArrayProxy<half, uint32_t>((half*)ptr)[0] = value;
        break;
    case TypeDesc::UINT8:
        DataArrayProxy<unsigned char, uint32_t>((unsigned char*)ptr)[0] = value;
        break;
    case TypeDesc::INT8:
        DataArrayProxy<char, uint32_t>((char*)ptr)[0] = value;
        break;
    case TypeDesc::UINT16:
        DataArrayProxy<unsigned short, uint32_t>((unsigned short*)ptr)[0]
            = value;
        break;
    case TypeDesc::INT16:
        DataArrayProxy<short, uint32_t>((short*)ptr)[0] = value;
        break;
    case TypeDesc::UINT:
        DataArrayProxy<uint32_t, uint32_t>((uint32_t*)ptr)[0] = value;
        break;
    case TypeDesc::INT:
        DataArrayProxy<int, uint32_t>((int*)ptr)[0] = value;
        break;
    case TypeDesc::UINT64:
        DataArrayProxy<uint64_t, uint32_t>((uint64_t*)ptr)[0] = value;
        break;
    case TypeDesc::INT64:
        DataArrayProxy<int64_t, uint32_t>((int64_t*)ptr)[0] = value;
        break;
    default:
        OIIO_ASSERT_MSG(0, "Unknown/unsupported data type %d",
                        channeltype(channel).basetype);
    }
}



cspan<TypeDesc>
DeepData::all_channeltypes() const
{
    OIIO_DASSERT(m_impl);
    return m_impl->m_channeltypes;
}



cspan<unsigned int>
DeepData::all_samples() const
{
    OIIO_DASSERT(m_impl);
    return m_impl->m_nsamples;
}



cspan<char>
DeepData::all_data() const
{
    OIIO_DASSERT(m_impl);
    m_impl->alloc(m_npixels);
    return m_impl->m_data;
}



void
DeepData::get_pointers(std::vector<void*>& pointers) const
{
    OIIO_DASSERT(m_impl);
    m_impl->alloc(m_npixels);
    pointers.resize(pixels() * channels());
    for (int64_t i = 0; i < m_npixels; ++i) {
        if (m_impl->m_nsamples[i])
            for (int c = 0; c < m_nchannels; ++c)
                pointers[i * m_nchannels + c] = (void*)m_impl->data_ptr(i, c,
                                                                        0);
        else
            for (int c = 0; c < m_nchannels; ++c)
                pointers[i * m_nchannels + c] = NULL;
    }
}



bool
DeepData::copy_deep_sample(int64_t pixel, int sample, const DeepData& src,
                           int64_t srcpixel, int srcsample)
{
    const void* srcdata = src.data_ptr(srcpixel, 0, srcsample);
    int nchans          = channels();
    if (!srcdata || nchans != src.channels())
        return false;
    int nsamples = src.samples(srcpixel);
    set_samples(pixel, std::max(samples(pixel), nsamples));
    for (int c = 0; c < m_nchannels; ++c) {
        if (channeltype(c) == TypeDesc::UINT32
            && src.channeltype(c) == TypeDesc::UINT32)
            set_deep_value(pixel, c, sample,
                           src.deep_value_uint(srcpixel, c, srcsample));
        else
            set_deep_value(pixel, c, sample,
                           src.deep_value(srcpixel, c, srcsample));
    }
    return true;
}



bool
DeepData::copy_deep_pixel(int64_t pixel, const DeepData& src, int64_t srcpixel)
{
    if (pixel < 0 || pixel >= pixels()) {
        // std::cout << "dst pixel was " << pixel << "\n";
        OIIO_DASSERT(0 && "Out of range pixel index");
        return false;
    }
    if (srcpixel < 0 || srcpixel >= src.pixels()) {
        // Copying empty pixel -- set samples to 0 and we're done
        // std::cout << "Source pixel was " << srcpixel << "\n";
        set_samples(pixel, 0);
        return true;
    }
    int nchans = channels();
    if (nchans != src.channels()) {
        OIIO_DASSERT(0 && "Number of channels don't match.");
        return false;
    }
    int nsamples = src.samples(srcpixel);
    set_samples(pixel, nsamples);
    if (nsamples == 0)
        return true;
    bool sametypes = samplesize() == src.samplesize();
    if (sametypes)
        for (int c = 0; c < nchans; ++c)
            sametypes &= (channeltype(c) == src.channeltype(c));
    if (sametypes)
        memcpy(data_ptr(pixel, 0, 0), src.data_ptr(srcpixel, 0, 0),
               samplesize() * nsamples);
    else {
        for (int c = 0; c < nchans; ++c) {
            if (channeltype(c) == TypeDesc::UINT32
                && src.channeltype(c) == TypeDesc::UINT32)
                for (int s = 0; s < nsamples; ++s)
                    set_deep_value(pixel, c, s,
                                   src.deep_value_uint(srcpixel, c, s));
            else
                for (int s = 0; s < nsamples; ++s)
                    set_deep_value(pixel, c, s, src.deep_value(srcpixel, c, s));
        }
    }
    return true;
}



bool
DeepData::split(int64_t pixel, float depth)
{
    using std::expm1;
    using std::log1p;
    bool splits_occurred = false;
    int zchan            = m_impl->m_z_channel;
    int zbackchan        = m_impl->m_zback_channel;
    if (zchan < 0)
        return false;  // No channel labeled Z -- we don't know what to do
    if (zbackchan < 0)
        return false;  // The samples are not extended -- nothing to split
    int nchans = channels();
    for (int s = 0; s < samples(pixel); ++s) {
        float zf = deep_value(pixel, zchan, s);      // z front
        float zb = deep_value(pixel, zbackchan, s);  // z back
        if (zf < depth && zb > depth) {
            // The sample spans depth, so split it.
            // See http://www.openexr.com/InterpretingDeepPixels.pdf
            splits_occurred = true;
            insert_samples(pixel, s + 1);
            copy_deep_sample(pixel, s + 1, *this, pixel, s);
            set_deep_value(pixel, zbackchan, s, depth);
            set_deep_value(pixel, zchan, s + 1, depth);
            // We have to proceed in two passes, since we may reuse the
            // alpha values, we can't overwrite them yet.
            for (int c = 0; c < nchans; ++c) {
                int alphachan = m_impl->m_myalphachannel[c];
                if (alphachan < 0       // No alpha
                    || alphachan == c)  // This is an alpha!
                    continue;
                float a = clamp(deep_value(pixel, alphachan, s), 0.0f, 1.0f);
                if (a == 1.0f)  // Opaque or channels without alpha, we're done.
                    continue;
                float xf = (depth - zf) / (zb - zf);
                float xb = (zb - depth) / (zb - zf);
                if (a > std::numeric_limits<float>::min()) {
                    float af  = -expm1(xf * log1p(-a));
                    float ab  = -expm1(xb * log1p(-a));
                    float val = deep_value(pixel, c, s);
                    set_deep_value(pixel, c, s, (af / a) * val);
                    set_deep_value(pixel, c, s + 1, (ab / a) * val);
                } else {
                    float val = deep_value(pixel, c, s);
                    set_deep_value(pixel, c, s, val * xf);
                    set_deep_value(pixel, c, s + 1, val * xb);
                }
            }
            // Now that we've adjusted the colors, do the alphas
            for (int c = 0; c < nchans; ++c) {
                int alphachan = m_impl->m_myalphachannel[c];
                if (alphachan != c)
                    continue;  // skip if not an alpha
                float a = clamp(deep_value(pixel, alphachan, s), 0.0f, 1.0f);
                if (a == 1.0f)  // Opaque or channels without alpha, we're done.
                    continue;
                float xf = (depth - zf) / (zb - zf);
                float xb = (zb - depth) / (zb - zf);
                if (a > std::numeric_limits<float>::min()) {
                    float af = -expm1(xf * log1p(-a));
                    float ab = -expm1(xb * log1p(-a));
                    set_deep_value(pixel, c, s, af);
                    set_deep_value(pixel, c, s + 1, ab);
                } else {
                    set_deep_value(pixel, c, s, a * xf);
                    set_deep_value(pixel, c, s + 1, a * xb);
                }
            }
        }
    }
    return splits_occurred;
}



namespace {

// Comparitor functor for depth sorting sample indices of a deep pixel.
class SampleComparator {
public:
    SampleComparator(const DeepData& dd, int pixel, int zchan, int zbackchan)
        : deepdata(dd)
        , pixel(pixel)
        , zchan(zchan)
        , zbackchan(zbackchan)
    {
    }
    bool operator()(int i, int j) const
    {
        // If either has a lower z, that's the lower
        float iz = deepdata.deep_value(pixel, zchan, i);
        float jz = deepdata.deep_value(pixel, zchan, j);
        if (iz < jz)
            return true;
        if (iz > jz)
            return false;
        // If both z's are equal, sort based on zback
        float izb = deepdata.deep_value(pixel, zbackchan, i);
        float jzb = deepdata.deep_value(pixel, zbackchan, j);
        return izb < jzb;
    }

private:
    const DeepData& deepdata;
    int pixel;
    int zchan, zbackchan;
};

}  // namespace



void
DeepData::sort(int64_t pixel)
{
    int zchan = m_impl->m_z_channel;
    if (zchan < 0)
        return;  // No channel labeled Z -- we don't know what to do
    int zbackchan = m_impl->m_z_channel;
    if (zbackchan < 0)
        zbackchan = zchan;
    int nsamples = samples(pixel);
    if (nsamples < 2)
        return;  // 0 or 1 samples -- no sort necessary

    // Ick, std::sort and friends take a custom comparator, but not a custom
    // swapper, so there's no way to std::sort a data type whose size is not
    // known at compile time. So we just sort the indices!
    int* sample_indices = OIIO_ALLOCA(int, nsamples);
    std::iota(sample_indices, sample_indices + nsamples, 0);
    std::stable_sort(sample_indices, sample_indices + nsamples,
                     SampleComparator(*this, pixel, zchan, zbackchan));

    // Now copy around using a temp buffer
    size_t samplebytes = samplesize();
    char* tmppixel     = OIIO_ALLOCA(char, samplebytes* nsamples);
    memcpy(tmppixel, data_ptr(pixel, 0, 0), samplebytes * nsamples);
    for (int i = 0; i < nsamples; ++i)
        memcpy(data_ptr(pixel, 0, i),
               tmppixel + samplebytes * sample_indices[i], samplebytes);
}



void
DeepData::merge_overlaps(int64_t pixel)
{
    using std::log1p;
    int zchan     = m_impl->m_z_channel;
    int zbackchan = m_impl->m_zback_channel;
    if (zchan < 0)
        return;  // No channel labeled Z -- we don't know what to do
    if (zbackchan < 0)
        zbackchan = zchan;  // Missing Zback -- use Z
    int nchans = channels();
    for (int s = 1 /* YES, 1 */; s < samples(pixel); ++s) {
        float zf = deep_value(pixel, zchan, s);      // z front
        float zb = deep_value(pixel, zbackchan, s);  // z back
        if (zf == deep_value(pixel, zchan, s - 1)
            && zb == deep_value(pixel, zbackchan, s - 1)) {
            // The samples overlap exactly, merge them per
            // See http://www.openexr.com/InterpretingDeepPixels.pdf
            for (int c = 0; c < nchans; ++c) {  // set the colors
                int alphachan = m_impl->m_myalphachannel[c];
                if (alphachan < 0)
                    continue;  // Not color or alpha
                if (alphachan == c)
                    continue;  // Adjust the alphas in a second pass below
                float a1 = (alphachan < 0)
                               ? 1.0f
                               : clamp(deep_value(pixel, alphachan, s - 1),
                                       0.0f, 1.0f);
                float a2 = (alphachan < 0)
                               ? 1.0f
                               : clamp(deep_value(pixel, alphachan, s), 0.0f,
                                       1.0f);
                float c1 = deep_value(pixel, c, s - 1);
                float c2 = deep_value(pixel, c, s);
                float am = a1 + a2 - a1 * a2;
                float cm;
                if (a1 == 1.0f && a2 == 1.0f)
                    cm = (c1 + c2) / 2.0f;
                else if (a1 == 1.0f)
                    cm = c1;
                else if (a2 == 1.0f)
                    cm = c2;
                else {
                    static const float MAX = std::numeric_limits<float>::max();
                    float u1               = -log1p(-a1);
                    float v1               = (u1 < a1 * MAX) ? u1 / a1 : 1.0f;
                    float u2               = -log1p(-a2);
                    float v2               = (u2 < a2 * MAX) ? u2 / a2 : 1.0f;
                    float u                = u1 + u2;
                    float w = (u > 1.0f || am < u * MAX) ? am / u : 1.0f;
                    cm      = (c1 * v1 + c2 * v2) * w;
                }
                set_deep_value(pixel, c, s - 1, cm);  // setting color
            }
            for (int c = 0; c < nchans; ++c) {  // set the alphas
                int alphachan = m_impl->m_myalphachannel[c];
                if (alphachan != c)
                    continue;  // This pass is only for alphas
                float a1 = (alphachan < 0)
                               ? 1.0f
                               : clamp(deep_value(pixel, alphachan, s - 1),
                                       0.0f, 1.0f);
                float a2 = (alphachan < 0)
                               ? 1.0f
                               : clamp(deep_value(pixel, alphachan, s), 0.0f,
                                       1.0f);
                float am = a1 + a2 - a1 * a2;
                set_deep_value(pixel, c, s - 1, am);  // setting alpha
            }
            // Now eliminate sample s and revisit again
            erase_samples(pixel, s, 1);
            --s;
        }
    }
}



void
DeepData::merge_deep_pixels(int64_t pixel, const DeepData& src, int srcpixel)
{
    int srcsamples = src.samples(srcpixel);
    if (srcsamples == 0)
        return;  // No samples to merge
    int dstsamples = samples(pixel);
    if (dstsamples == 0) {
        // Nothing in our pixel yet, so just copy src's pixel
        copy_deep_pixel(pixel, src, srcpixel);
        return;
    }

    // Need to merge the pixels

    // First, merge all of src's samples into our pixel
    set_samples(pixel, dstsamples + srcsamples);
    for (int i = 0; i < srcsamples; ++i)
        copy_deep_sample(pixel, dstsamples + i, src, srcpixel, i);

    // Now ALL the samples from both images are in our pixel.
    // Mutually split the samples against each other.
    sort(pixel);  // sort first so we only loop once
    int zchan     = m_impl->m_z_channel;
    int zbackchan = m_impl->m_zback_channel;
    for (int s = 0; s < samples(pixel); ++s) {
        float z     = deep_value(pixel, zchan, s);
        float zback = deep_value(pixel, zbackchan, s);
        split(pixel, z);
        split(pixel, zback);
    }
    sort(pixel);

    // Now merge the overlaps
    merge_overlaps(pixel);
}



float
DeepData::opaque_z(int64_t pixel) const
{
    if (pixel < 0)
        return std::numeric_limits<float>::max();
    int nsamples = samples(pixel);
    int cZ       = Z_channel();
    if (!nsamples || cZ < 0) {
        // If nothing is in this pixel or we don't have Z's, just
        // return a huge number.
        return std::numeric_limits<float>::max();
    }

    int cZback = Zback_channel();  // Will be Z if Zback is missing
    int cA     = A_channel();
    int cAR    = AR_channel();  // A[RGB]_channel() returns A_channel if the
    int cAG    = AG_channel();  // specific channel is missing.
    int cAB    = AB_channel();
    if (cAR < 0 || cAG < 0 || cAB < 0) {
        // If there aren't alpha channels, just return the closest Z
        return deep_value(pixel, cZ, 0);
    }

    // There are samples, Z, and alpha channels. Figure out where it gets
    // opaque.
    for (int s = 0; s < nsamples; ++s) {
        float alpha;
        if (cA >= 0)
            alpha = deep_value(pixel, cA, s);
        else {
            alpha = (deep_value(pixel, cAR, s) + deep_value(pixel, cAG, s)
                     + deep_value(pixel, cAB, s))
                    / 3.0f;
        }
        if (alpha >= 1.0f) {
            // We hit an opaque sample. Return its far side.
            return deep_value(pixel, cZback, s);
        }
    }
    // We never hit an opaque sample. Return huge number.
    return std::numeric_limits<float>::max();
}



void
DeepData::occlusion_cull(int64_t pixel)
{
    int alpha_channel = m_impl->m_alpha_channel;
    if (alpha_channel < 0)
        return;  // If there isn't a definitive alpha channel, never mind
    int nsamples = samples(pixel);
    for (int s = 0; s < nsamples; ++s) {
        if (deep_value(pixel, alpha_channel, s) >= 1.0f) {
            // We hit an opaque sample. Cull everything farther.
            set_samples(pixel, s + 1);
            break;
        }
    }
}


OIIO_NAMESPACE_END
